package org.judo.components;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.judo.admin.identity.AppUser;
import org.judo.application.Application;
import org.judo.database.Database;
import org.judo.database.JudoDBConnection;
import org.judo.database.exceptions.DatabaseException;
import org.judo.propertyobject.PropertyObject;
import org.judo.service.JudoService;

public class JudoComponent extends PropertyObject
{
	private boolean inited = false;
	private static long nextId;
	
	private long creationTime;
	protected Application application;
	protected HashMap connections = new HashMap();
	protected ArrayList loadedComponents = new ArrayList();
	protected long localTransactionId; 
	
	
	public void inited(boolean value)
	{
		inited = value;
	}
	
	public boolean inited()
	{
		return inited;
	}
	
	public void closeGroup()
	{
		
	}
	
	public static synchronized long obtainTransactionId()
	{
		return nextId++;
	}
	
	public void populateTransactionId(long id)
	{
		localTransactionId = id;
	}
	
	public long localTransactionId()
	{
		return localTransactionId;
	}
	
	public void populateConnection(String name,JudoDBConnection con)
	{
		connections.put(name,con);
	}
	
	public void populateApplication(Application app)
	{
		application = app;
	}

	public long creationTime()
	{
		return creationTime;
	}

	public void creationTime(long creationTime)
	{
		this.creationTime = creationTime;
	}

	public Application application()
	{
		return application;
	}
	
	//TODO: Need to fix
	public JudoComponent loadComponent(Class type)
		throws ComponentCreationException
	{
		JudoComponent component = ComponentManager.loadComponent(null,type,connections);
		
		long transactionId = this.localTransactionId();
		component.populateTransactionId(transactionId);
		
		loadedComponents.add(component);
		return component;
	}
	
	public JudoComponent loadComponentAsNewTransaction(Class type)
		throws ComponentCreationException
	{
		JudoComponent component = ComponentManager.loadComponent(null,type,null);
		
		long transactionId = JudoComponent.obtainTransactionId();
		component.populateTransactionId(transactionId);
		
		loadedComponents.add(component);
		return component;
	}
	
	public void commitAllTransactions() 
	{
		Iterator connectionNames = connections.keySet().iterator();
		
		while (connectionNames.hasNext())
		{
			String name = (String)connectionNames.next();
		
			JudoDBConnection con = 
				(JudoDBConnection)connections.get(name);
			
			try
			{
				if (!con.hasFinalized())
					con.commitTransaction();
			} 
			catch (DatabaseException e)
			{
				rollbackAllTransactions();
			}
		}
		
		for (int i=0;i<loadedComponents.size();i++)
		{
			JudoComponent component =
				(JudoComponent)loadedComponents.get(i);
		
			component.commitAllTransactions();
		}
	}
	
	public void rollbackAllTransactions()
	{
		rollbackAllTransactions(localTransactionId);
	}
	
	public void rollbackAllTransactions(long transactionId) 
	{
		Iterator connectionNames = connections.keySet().iterator();
		
		while (connectionNames.hasNext())
		{
			String name = (String)connectionNames.next();
			
			JudoDBConnection con = 
				(JudoDBConnection)connections.get(name);
			
			try
			{
				if (!con.hasFinalized())
				{
					if (transactionId == localTransactionId)
						if (!con.hasFinalized())
							con.rollbackTransaction();
					else
						if (!con.hasFinalized())
							con.commitTransaction();
				}
			} 
			catch (DatabaseException e)
			{
				e.printStackTrace();
			}
		}
		
		for (int i=0;i<loadedComponents.size();i++)
		{
			JudoComponent component =
				(JudoComponent)loadedComponents.get(i);
		
			component.rollbackAllTransactions(localTransactionId);
		}
	}
	
	public void completeProcessing()
	{
		try
		{
			afterRequestCompleted();
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		
		commitAllTransactions();
		
		for (int i=0;i<loadedComponents.size();i++)
		{
			JudoComponent component =
				(JudoComponent)loadedComponents.get(i);
			
			component.completeProcessing();
		}
		
		loadedComponents.clear();
		connections.clear();
		localTransactionId = 0;
	}
	
	public List<String> serviceNames()
	{
		List<String> services = new ArrayList<String>();
		
		Field fields[] = this.getClass().getDeclaredFields();
		
		for (int i=0;i<fields.length;i++)
		{
			Field field = fields[i];
			
			if (JudoService.class.isAssignableFrom(field.getType()))
				services.add(field.getName());
		}
		
		return services;
	}
	
	public boolean declaresUser()
	{
		try
		{
			Field field = this.getClass().getDeclaredField("user");
			return true;
		}
		catch(NoSuchFieldException e)
		{
			return false;
		}
	}
	
	public void populateUser(AppUser user)
	{
		try
		{
			Field field = this.getClass().getDeclaredField("user");
			field.setAccessible(true);
			field.set(this,user);
		} 
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}
	
	public void populateService(String fieldName,JudoService service)
	{
		try
		{
			Field field = this.getClass().getDeclaredField(fieldName);
			field.setAccessible(true);
			field.set(this,service);
		} 
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}
	
	public Class serviceType(String name)
	{
		Class type = null;
		
		try
		{
			Field field = this.getClass().getDeclaredField(name);
			type = field.getType();
		} 
		catch (Exception e)
		{
			e.printStackTrace();
		}
		
		return type;
	}
	
	public List<String> databaseNames()
	{
		List<String> dbs = new ArrayList<String>();
		
		Field fields[] = this.getClass().getDeclaredFields();
		
		for (int i=0;i<fields.length;i++)
		{
			Field field = fields[i];
			
			if (Database.class.isAssignableFrom(field.getType()))
				dbs.add(field.getName());
		}
		
		return dbs;
	}
	
	public void populateDatabase(String fieldName,Database db)
	{
		try
		{
			Field field = this.getClass().getDeclaredField(fieldName);
			field.setAccessible(true);
			field.set(this,db);
		} 
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}
	
	public Class databaseType(String name)
	{
		Class type = null;
		
		try
		{
			Field field = this.getClass().getDeclaredField(name);
			type = field.getType();
		} 
		catch (Exception e)
		{
			e.printStackTrace();
		}
		
		return type;
	}
	
	public List<String> sessionNames()
	{
		List<String> sessions = new ArrayList<String>();
		
		Field fields[] = this.getClass().getDeclaredFields();
		
		for (int i=0;i<fields.length;i++)
		{
			Field field = fields[i];
			
			if (Session.class.isAssignableFrom(field.getType()))
				sessions.add(field.getName());
		}
		
		return sessions;
	}
	
	public Class sessionType(String name)
	{
		Class type = null;
		
		try
		{
			Field field = this.getClass().getDeclaredField(name);
			type = field.getType();
		} 
		catch (Exception e)
		{
			e.printStackTrace();
		}
		
		return type;
	}
	
	public void populateSession(String fieldName,Session session)
	{
		try
		{
			Field field = this.getClass().getDeclaredField(fieldName);
			field.setAccessible(true);
			field.set(this,session);
		} 
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}
	
	/*
	 * ************************************************
	 * ***************LIFECYCLE METHODS****************
	 * ************************************************
	 */
	
	public void componentCreated()throws Exception {} 
	public void afterRequestCompleted()throws Exception{}
}
